﻿/*
 * MIT License
 *
 * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
 *
 * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifdef ENABLE_MP4V2
#include <ctime>
#include <sys/stat.h>
#include "Common/config.h"
#include "Mp4Maker.h"
#include "MediaRecorder.h"
#include "Util/File.h"
#include "Util/mini.h"
#include "Util/util.h"
#include "Util/NoticeCenter.h"
#include "Extension/H264.h"
#include "Extension/AAC.h"
#include "Thread/WorkThreadPool.h"
#include <stdio.h>
#include <dirent.h>
#include <regex>
#include "jsoncpp/json.h"
#include <vector>

using namespace Json;
using namespace toolkit;

namespace mediakit {

string timeStr(const char *fmt) {
	std::tm tm_snapshot;
	auto time = ::time(NULL);
#if defined(_WIN32)
	localtime_s(&tm_snapshot, &time); // thread-safe
#else
	localtime_r(&time, &tm_snapshot); // POSIX
#endif
	const size_t size = 1024;
	char buffer[size];
	auto success = std::strftime(buffer, size, fmt, &tm_snapshot);
	if (0 == success)
		return string(fmt);
	return buffer;
}

string timeStr2(std::time_t rawtime, const char *fmt) {
    std::tm tm_snapshot;
    auto time = ::time(NULL);
#if defined(_WIN32)
    localtime_s(&tm_snapshot, &time); // thread-safe
#else
    localtime_r(&rawtime, &tm_snapshot); // POSIX
#endif
    const size_t size = 1024;
    char buffer[size];
    auto success = std::strftime(buffer, size, fmt, &tm_snapshot);
    if (0 == success)
        return string(fmt);
    return buffer;
}


time_t string2time(const std::string& timeStr){
    struct tm stTm;
    sscanf(timeStr.c_str(), "%4d%2d%2d",
           &(stTm.tm_year),
           &(stTm.tm_mon),
           &(stTm.tm_mday));

    stTm.tm_year -= 1900;
    stTm.tm_mon--;
    stTm.tm_isdst = -1;
    stTm.tm_hour = 0;
    stTm.tm_min = 0;
    stTm.tm_sec = 0;

    return mktime(&stTm);
}


Mp4Maker::Mp4Maker(const string& strPath,
				   const string &strVhost,
				   const string &strApp,
				   const string &strStreamId,
				   //chenxiaolei 修改为int, 录像最大录制天数,0就是不录
				   const int &recordMp4) {
	DebugL << strPath;
	_strPath = strPath;
	_recordMp4 = recordMp4;

	/////record 业务逻辑//////
	_info.strAppName = strApp;
	_info.strStreamId = strStreamId;
	_info.strVhost = strVhost;
	_info.strFolder = strPath;
	//----record 业务逻辑----//
}
Mp4Maker::~Mp4Maker() {
	closeFile();
}

void Mp4Maker::inputH264(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp){
	auto iType = H264_TYPE(((uint8_t*)pData)[0]);
	switch (iType) {
	case H264Frame::NAL_B_P: //P
	case H264Frame::NAL_IDR: { //IDR
		if (_strLastVideo.size()) {
			int64_t iTimeInc = (int64_t)ui32TimeStamp - (int64_t)_ui32LastVideoTime;
			iTimeInc = MAX(0,MIN(iTimeInc,500));
			if(iTimeInc == 0 ||  iTimeInc == 500){
				WarnL << "abnormal time stamp increment:" << ui32TimeStamp << " " << _ui32LastVideoTime;
			}
			inputH264_l((char *) _strLastVideo.data(), _strLastVideo.size(), iTimeInc);
		}

		uint32_t prefixe  = htonl(ui32Length);
		_strLastVideo.assign((char *) &prefixe, 4);
		_strLastVideo.append((char *)pData,ui32Length);

		_ui32LastVideoTime = ui32TimeStamp;
	}
		break;
	default:
		break;
	}
}
void Mp4Maker::inputAAC(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp){
	if (_strLastAudio.size()) {
		int64_t iTimeInc = (int64_t)ui32TimeStamp - (int64_t)_ui32LastAudioTime;
		iTimeInc = MAX(0,MIN(iTimeInc,500));
		if(iTimeInc == 0 ||  iTimeInc == 500){
			WarnL << "abnormal time stamp increment:" << ui32TimeStamp << " " << _ui32LastAudioTime;
		}
		inputAAC_l((char *) _strLastAudio.data(), _strLastAudio.size(), iTimeInc);
	}
	_strLastAudio.assign((char *)pData, ui32Length);
	_ui32LastAudioTime = ui32TimeStamp;
}

void Mp4Maker::inputH264_l(void *pData, uint32_t ui32Length, uint32_t ui32Duration) {
    GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
	auto iType =  H264_TYPE(((uint8_t*)pData)[4]);

	//chenxiaolei 确保录像,不会跨天
	if(iType == H264Frame::NAL_IDR && (_hMp4 == MP4_INVALID_FILE_HANDLE || _ticker.elapsedTime() > recordSec * 1000 || timeStr("%H%M%S")=="000000")){
		//在I帧率处新建MP4文件
		//如果文件未创建或者文件超过recordSec秒(且不跨天)则创建新文件
		createFile();
	}
	if (_hVideo != MP4_INVALID_TRACK_ID) {
		MP4WriteSample(_hMp4, _hVideo, (uint8_t *) pData, ui32Length,ui32Duration * 90,0,iType == 5);
	}
}

void Mp4Maker::inputAAC_l(void *pData, uint32_t ui32Length, uint32_t ui32Duration) {
    GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);

    //chenxiaolei 确保录像,不会跨天
    if (!_haveVideo && (_hMp4 == MP4_INVALID_FILE_HANDLE || _ticker.elapsedTime() > recordSec * 1000 || timeStr("%H%M%S")=="000000")) {
		//在I帧率处新建MP4文件
        //如果文件未创建或者文件超过recordSec秒(且不跨天)则创建新文件
		createFile();
	}
	if (_hAudio != MP4_INVALID_TRACK_ID) {
		auto duration = ui32Duration * _audioSampleRate /1000.0;
		MP4WriteSample(_hMp4, _hAudio, (uint8_t*)pData, ui32Length,duration,0,false);
	}
}

void Mp4Maker::createFile() {
	closeFile();

    //chenxiaolei 录像父文件夹格式调整
	auto strDate = timeStr("%Y%m%d");
	auto strTime = timeStr("%H-%M-%S");
	auto strFileTmp = _strPath + strDate + "/." + strTime + ".mp4";
	auto strFile =	_strPath + strDate + "/" + strTime + ".mp4";

	/////record 业务逻辑//////
	_info.ui64StartedTime = ::time(NULL);
	_info.strFileName = strTime + ".mp4";
	_info.strFilePath = strFile;

    GET_CONFIG(string,appName,Record::kAppName);

    _info.strUrl = appName + "/"
                   + _info.strAppName + "/"
                   + _info.strStreamId + "/"
                   + strDate + "/"
                   + strTime + ".mp4";

	//----record 业务逻辑----//

#if !defined(_WIN32)
	File::createfile_path(strFileTmp.data(), S_IRWXO | S_IRWXG | S_IRWXU);
#else
	File::createfile_path(strFileTmp.data(), 0);
#endif
	_hMp4 = MP4Create(strFileTmp.data());
	if (_hMp4 == MP4_INVALID_FILE_HANDLE) {
		WarnL << "创建MP4文件失败:" << strFileTmp;
		return;
	}
	//MP4SetTimeScale(_hMp4, 90000);
	_strFileTmp = strFileTmp;
	_strFile = strFile;
	_ticker.resetTime();

	auto videoTrack = dynamic_pointer_cast<H264Track>(getTrack(TrackVideo));
	if(videoTrack){
		auto &sps = videoTrack->getSps();
		auto &pps = videoTrack->getPps();
		_hVideo = MP4AddH264VideoTrack(_hMp4,
									   90000,
									   MP4_INVALID_DURATION,
									   videoTrack->getVideoWidth(),
									   videoTrack->getVideoHeight(),
									   sps[1],
									   sps[2],
									   sps[3],
									   3);
		if(_hVideo != MP4_INVALID_TRACK_ID){
			MP4AddH264SequenceParameterSet(_hMp4, _hVideo, (uint8_t *)sps.data(), sps.size());
			MP4AddH264PictureParameterSet(_hMp4, _hVideo, (uint8_t *)pps.data(), pps.size());
		}else{
			WarnL << "添加视频通道失败:" << strFileTmp;
		}
	}

	auto audioTrack = dynamic_pointer_cast<AACTrack>(getTrack(TrackAudio));
	if(audioTrack){
		_audioSampleRate = audioTrack->getAudioSampleRate();
		_hAudio = MP4AddAudioTrack(_hMp4, _audioSampleRate, MP4_INVALID_DURATION, MP4_MPEG4_AUDIO_TYPE);
		if (_hAudio != MP4_INVALID_TRACK_ID) {
			auto &cfg =  audioTrack->getAacCfg();
			MP4SetTrackESConfiguration(_hMp4, _hAudio,(uint8_t *)cfg.data(), cfg.size());
		}else{
			WarnL << "添加音频通道失败:" << strFileTmp;
		}
	}
}

void Mp4Maker::asyncClose() {
	auto hMp4 = _hMp4;
	auto strFileTmp = _strFileTmp;
	auto strFile = _strFile;
    auto info = _info;
    //chenxiaolei 支持删除过期录像
    auto strPath = _strPath;
    auto recordMp4 = _recordMp4;
	WorkThreadPool::Instance().getExecutor()->async([hMp4,strFileTmp,strFile,strPath,recordMp4,info]() {
		//获取文件录制时间，放在MP4Close之前是为了忽略MP4Close执行时间
        InfoL << "获取文件录制时间";
		const_cast<Mp4Info&>(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime;
		//MP4Close非常耗时，所以要放在后台线程执行
        InfoL << "台线程执行MP4Close";
		MP4Close(hMp4,MP4_CLOSE_DO_NOT_COMPUTE_BITRATE);

        InfoL << "临时文件名改成正式文件名，防止mp4未完成时被访问: rename " << strFileTmp.data() << ">" << strFile.data();
		//临时文件名改成正式文件名，防止mp4未完成时被访问
		rename(strFileTmp.data(),strFile.data());

        InfoL << "删除过期录像";
		//chenxiaolei 删除过期录像
        if(recordMp4){
            auto curTimeStr = timeStr("%Y%m%d");

            DIR *dr = opendir(strPath.data());
            if (dr != NULL)  {
                std::vector<string> delDirPaths;
                struct dirent *de;
                while ((de = readdir(dr)) != NULL){
                    if(!std::regex_match(de->d_name, std::regex("\\d{4}\\d{2}\\d{2}"))){
                        continue;
                    }
                    auto curTime=string2time(curTimeStr);
                    auto targetTime=string2time(de->d_name);
                    double dSec= std::difftime(curTime, targetTime);
                    double dDay= dSec/60/60/24;
                    if( dDay >  recordMp4){
                        /*auto delDirPath= (strPath + (de->d_name)).data();
                        File::delete_file(delDirPath);*/
                        auto delDirPath= (strPath + (de->d_name));
                        delDirPaths.emplace_back(delDirPath);

                        //system(("rm -rf " + delDirPath+"").data());
                        //WarnL << "删除过期录像文件:"<< (strPath + (de->d_name)).data() ;
                    }
                }
                closedir(dr);

                for (auto val : delDirPaths){
                    File::delete_file(val.data());
                    WarnL << "删除过期录像文件:"<< val;
                }
            }
        }

		//获取文件大小
		struct stat fileData;
		stat(strFile.data(), &fileData);
		const_cast<Mp4Info&>(info).ui64FileSize = fileData.st_size;


		//chenxiaolei 生成录像文件的信息文件(记录录像时长,开始时间,持续时长等)
        InfoL << "生成录像文件的信息文件";
        Json::Value infoJson;
        infoJson["startAt"] = timeStr2(info.ui64StartedTime,"%Y%m%d%H%M%S");
        infoJson["duration"] = (int)(info.ui64TimeLen) ;
        infoJson["mp4"] = info.strUrl;

        auto strInfoFile =	strFile +".json";
        ofstream os;
        os.open(strInfoFile);
        Json::StyledWriter sw;
        os << sw.write(infoJson);
        os.close();
        InfoL << "emitEvent kBroadcastRecordMP4";
		/////record 业务逻辑//////
		NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4,info);
	});
}

void Mp4Maker::closeFile() {
	if (_hMp4 != MP4_INVALID_FILE_HANDLE) {
		asyncClose();
		_hMp4 = MP4_INVALID_FILE_HANDLE;
		_hVideo = MP4_INVALID_TRACK_ID;
		_hAudio = MP4_INVALID_TRACK_ID;
	}
}

void Mp4Maker::onTrackFrame(const Frame::Ptr &frame) {
	switch (frame->getCodecId()){
		case CodecH264:{
			inputH264(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(),frame->stamp());
		}
			break;
		case CodecAAC:{
			inputAAC(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(),frame->stamp());
		}
			break;

		default:
			break;
	}
}

void Mp4Maker::onAllTrackReady() {
	_haveVideo = getTrack(TrackVideo).operator bool();
}

} /* namespace mediakit */


#endif //ENABLE_MP4V2
